package runninbuddy;

import java.io.IOException;

import javax.microedition.location.LocationException;
import javax.microedition.location.QualifiedCoordinates;

import net.rim.device.api.gps.BlackBerryCriteria;
import net.rim.device.api.gps.BlackBerryLocation;
import net.rim.device.api.gps.GPSInfo;
import net.rim.device.api.io.MalformedURIException;
import net.rim.device.api.io.URI;
import net.rim.device.api.system.AccelerometerData;
import net.rim.device.api.system.Display;
import net.rim.device.api.ui.Field;
import net.rim.device.api.ui.MenuItem;
import net.rim.device.api.ui.Ui;
import net.rim.device.api.ui.UiApplication;
import net.rim.device.api.ui.component.DateField;
import net.rim.device.api.ui.component.EditField;
import net.rim.device.api.ui.component.LabelField;
import net.rim.device.api.ui.component.SeparatorField;
import net.rim.device.api.ui.component.TextField;
import net.rim.device.api.ui.container.MainScreen;

public class RunninBuddy extends UiApplication implements Updatable {
	private static final String CSV_EXT = ".csv";
	private static final BlackBerryCriteria criteria = new BlackBerryCriteria(
			GPSInfo.GPS_MODE_AUTONOMOUS);
	static {
		criteria.setAltitudeRequired(true);
		criteria.setSpeedAndCourseRequired(true);
	}

	private static final String _csvRoot = "file:///SDCard/";

	private final DateField _dateField;
	private final TextField _longitudeField;
	private final TextField _latitudeField;
	private final TextField _altitudeField;
	private final TextField _speedField;
	private final TextField _courseField;
	private final TextField _timestampDiff;
	private final TextField _xAcc;
	private final TextField _yAcc;
	private final TextField _zAcc;
	private final LabelField _debugField;
	private final MainScreen _screen;

	private DataStore dataStore;
	private DataLogger dataLogger;

	private DataCombiner dataCombiner;
	private DataGrabber dataGrabber;

	private DataExchanger dataExchanger;
	private Thread dataLoggerThread;
	private Thread dataGrabberThread;
	private Thread dataExchangerThread;
	// TODO modify these through an options screen
	private static final int interval = 1;
	private static final int timeout = 1;
	private static final int maxAge = 1;

	public RunninBuddy() {

		_dateField = new DateField("Tme: ", System.currentTimeMillis(),
				DateField.TIME | Field.READONLY | Field.NON_FOCUSABLE);

		_longitudeField = new EditField("Lng: ", "NaN");
		_longitudeField.setEditable(false);

		_latitudeField = new EditField("Lat: ", "NaN");
		_latitudeField.setEditable(false);

		_altitudeField = new EditField("Alt: ", "NaN");
		_altitudeField.setEditable(false);

		_speedField = new EditField("Spd: ", "NaN");
		_speedField.setEditable(false);

		_courseField = new EditField("Crs: ", "NaN");
		_courseField.setEditable(false);

		_timestampDiff = new EditField("TSd: ", "NaN");
		_timestampDiff.setEditable(false);

		_xAcc = new EditField("XAc: ", "NaN");
		_xAcc.setEditable(false);

		_yAcc = new EditField("YAc: ", "NaN");
		_yAcc.setEditable(false);

		_zAcc = new EditField("ZAc: ", "NaN");
		_zAcc.setEditable(false);

		_debugField = new LabelField();
		_screen = new MainScreen();

		updateScreenTitle();
		_screen.add(_dateField);
		_screen.add(_longitudeField);
		_screen.add(_latitudeField);
		_screen.add(_altitudeField);
		_screen.add(_speedField);
		_screen.add(_courseField);
		_screen.add(_timestampDiff);
		_screen.add(_xAcc);
		_screen.add(_yAcc);
		_screen.add(_zAcc);
		_screen.add(new SeparatorField());
		_screen.add(_debugField);

		MenuItem multipleFixMenu = new MenuItem("Start Multiple Fix", 1, 1) {
			public void run() {
				updateScreenTitle();
				getMultipleFix();
			}
		};
		MenuItem stopMultipleFixMenu = new MenuItem("Stop Multiple Fix", 1, 1) {
			public void run() {
				stop();
			}
		};

		_screen.addMenuItem(multipleFixMenu);
		_screen.addMenuItem(stopMultipleFixMenu);
		Ui.getUiEngineInstance().setAcceptableDirections(
				Display.DIRECTION_NORTH);
		pushScreen(_screen);
	}

	public boolean requestClose() {
		stop();
		return super.requestClose();
	}

	private void stopLogger() {
		if (dataLogger != null)
			try {
				dataLogger.stop();
			} catch (IOException e) {
				dbg("stopMultipleFix: " + e.getMessage());
			} finally {
				if (dataLoggerThread != null)
					dataLoggerThread.interrupt();
			}
	}

	private void stopExchanger() {
		if (dataExchanger != null) {
			dataExchanger.stop();
			if (dataExchangerThread != null)
				dataExchangerThread.interrupt();
		}
	}

	private void stopGrabber() {
		if (dataGrabber != null) {
			dataGrabber.stop();
			if (dataGrabberThread != null)
				dataGrabberThread.interrupt();
		}
	}

	private void stop() {
		stopLogger();
		stopExchanger();
		stopGrabber();
	}

	private void getMultipleFix() {
		stop();
		try {
			initOutput();
			dataLoggerThread = new Thread(dataLogger);
			dataLoggerThread.start();
		} catch (Exception e) {
			dbg("getMultipleFix: " + e.getMessage());
		}
		try {
			initInput();
			dataGrabberThread = new Thread(dataGrabber);
			dataGrabberThread.start();
			initExchanger();
			dataExchangerThread = new Thread(dataExchanger);
			dataExchangerThread.start();
		} catch (LocationException e) {
			dbg("getMultipleFix: " + e.getMessage());
			stop();
		}
	}

	private void initExchanger() {
		dataExchanger = new DataExchanger(dataCombiner, dataStore, this);
	}

	private void initOutput() throws IllegalArgumentException, IOException,
			MalformedURIException {
		String fileURL = buildFileURL();
		dataStore = new DataStore();
		dataLogger = new DataLogger(dataStore, URI.create(fileURL));
	}

	private String buildFileURL() {
		// TODO should use a file picker
		StringBuffer sb = new StringBuffer();
		sb.append(_csvRoot);
		sb.append(System.currentTimeMillis());
		sb.append(CSV_EXT);
		return sb.toString();
	}

	private void initInput() throws LocationException {
		dataCombiner = new DataCombiner();
		dataGrabber = new DataGrabber(dataCombiner, criteria, interval,
				timeout, maxAge);
	}

	private void updateScreenTitle() {
		_screen.setTitle(new DateField("Runnin' Buddy", System
				.currentTimeMillis(), DateField.TIME | Field.READONLY
				| Field.NON_FOCUSABLE));

	}

	public static void main(String[] args) {
		RunninBuddy app = new RunninBuddy();
		app.enterEventDispatcher();
	}

	public void update(final RunnerData data) {
		invokeLater(new Runnable() {
			public void run() {
				if (data != null && data.isValid()) {
					BlackBerryLocation location = data.bbLocation;
					AccelerometerData accData = data.accData;
					QualifiedCoordinates coordinates = location
							.getQualifiedCoordinates();
					_dateField.setDate(location.getTimestamp());
					_longitudeField.setText(String.valueOf(coordinates
							.getLongitude()));
					_latitudeField.setText(String.valueOf(coordinates
							.getLatitude()));
					_altitudeField.setText(String.valueOf(coordinates
							.getAltitude()));
					_speedField.setText(String.valueOf(location.getSpeed()));
					_courseField.setText(String.valueOf(location.getCourse()));
					_timestampDiff.setText(String.valueOf(accData
							.getLastTimestamp()
							- location.getTimestamp()));
					_xAcc.setText(String
							.valueOf(accData.getLastXAcceleration()));
					_yAcc.setText(String
							.valueOf(accData.getLastYAcceleration()));
					_zAcc.setText(String
							.valueOf(accData.getLastZAcceleration()));
				} else {
					dbg("updateUI: Invalid RunnerData was received.");
				}
			}
		});
	}

	private void dbg(final String msg) {
		invokeLater(new Runnable() {
			public void run() {
				StringBuffer sb = new StringBuffer();
				sb.append(msg);
				if (_debugField.getText().length() > 0) {
					sb.append('\n');
					sb.append(_debugField.getText());
				}
				_debugField.setText(sb.toString());
				System.err.println(msg);
			}
		});
	}
}
